using System;
using System.Collections.Generic;
using System.Linq;

namespace Think.Problems
{
    public class Deck : IDeck
    {
        private readonly Queue<string> _cards = null;

        /// <summary>
        /// Construct
        /// </summary>
        public Deck()
        {
            _cards = new Queue<string>(Cards());
        }

        /// <summary>
        /// Construct
        /// </summary>
        /// <param name="a_seed">Deck seed.</param>
        public Deck(int a_seed)
        {
            var list = new List<string>(Cards());
            _cards = new Queue<string>(52);

            var random = new Random(a_seed);
            while (list.Any())
            {
                var nextIdx = random.Next(list.Count);
                _cards.Enqueue(list[nextIdx]);
                list.RemoveAt(nextIdx);
            }
        }

        /// <summary>
        /// Construct
        /// </summary>
        /// <param name="a_cards">Cards</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_cards"/>" is null.</exception>
        public Deck(IEnumerable<string> a_cards)
        {
            #region Argument Validation

            if (a_cards == null)
                throw new ArgumentNullException("a_cards");

            #endregion            

            _cards = new Queue<string>();
            foreach (var card in a_cards)
                ReturnToBottom(card);
        }

        /// <summary>
        /// Value of cards in this deck.
        /// </summary>
        public int Count
        {
            get { return _cards.Count; }
        }

        /// <summary>
        /// Draw one card from this deck.
        /// </summary>
        /// <returns>Card code.</returns>
        public string Draw()
        {
            if (!_cards.Any())
                return null;

            return _cards.Dequeue();
        }

        /// <summary>
        /// Draw the given number of cards from the deck (<paramref name="a_count"/>).
        /// </summary>
        /// <param name="a_count">Number of cards.</param>
        /// <returns>Cards drawn.</returns>
        public String[] Draw(int a_count)
        {
            a_count = Math.Min(a_count, _cards.Count);
            var cards = new String[a_count];

            for (var i = 0; i < a_count; i++)
                cards[i] = _cards.Dequeue();

            return cards;
        }

        /// <summary>
        /// Return the given cards (<paramref name="a_cards"/>) to the bottom of this deck if it is a valid card and 
        ///     does not already exist in this deck.
        /// </summary>
        /// <param name="a_cards">Cards to return.</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_cards"/>" is null.</exception>
        public void ReturnToBottom(params string[] a_cards)
        {
            #region Argument Validation

            if (a_cards == null || a_cards.Any(i => i == null))
                throw new ArgumentNullException("a_cards");

            #endregion

            foreach (var card in a_cards)
                ReturnToBottom(card);
        }

        /// <summary>
        /// Add the given card (<paramref name="a_card"/>) to the bottom of this deck if it is a valid card and does
        ///     not already exist in this deck.
        /// </summary>
        /// <param name="a_card"></param>
        public void ReturnToBottom(string a_card)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            var allCards = Cards();

            if (!allCards.Contains(a_card))
                return;

            if (!_cards.Contains(a_card))
                _cards.Enqueue(a_card);
        }

        /// <summary>
        /// Get all of the card in an unsorted deck.
        /// </summary>
        /// <returns>Cards in an unsorted deck.</returns>
        private static string[] Cards()
        {
            return new[]
            {
                "AH", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H", "JH", "QH", "KH",
                "AC", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "10C", "JC", "QC", "KC",
                "AD", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "10D", "JD", "QD", "KD",
                "AS", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "JS", "QS", "KS",
            };
        }

        /// <summary>
        /// Clone this deck.
        /// </summary>
        /// <returns>Clone of deck.</returns>
        public Deck Clone()
        {
            return new Deck(this._cards);
        }
    }
}